import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * This is the SimpleWebServer class. The main method in SimpleWebServerRun will create an instance of this class
 * and call run method of this class to run the server
 * @author Minh-Tuan Ta
 *
 */

public class SimpleWebServer {

  //instance of web config object
  private SimpleWebServerConfig config;
  
  //the type of the current request file (in case it is an image, just output the path)
  private String ctype;
  
  //the path the requested file
  private String filePath;
  
  private boolean running;
  
  private ServerSocket serverSocket;
  
  public ServerSocket getServerSocket()
  {
    return serverSocket;
  }
  
  public void setRunning(boolean r)
  {
    running = r;
  }

  //Constructor. Create and load the SimpleWebServerConfig that store the configurations of the server
  public SimpleWebServer()
  {
    config = new SimpleWebServerConfig();

    //load the config file
    config.loadConfigFromXML("config.xml");
  }

  /**
   * run method will run the server and handle the request and response.
   * @param args
   */
  public void run() {
    // TODO Auto-generated method stub

    //only work if the config is valid
    if (config.isConfigValid())
    {
      try
      {
        //Create a server socket
        serverSocket = new ServerSocket(config.getServerPort());

        System.out.println("Simple Web Server Started");
        System.out.println("Your document root is " + config.getDocumentRoot());
        System.out.println("Your server port is " + config.getServerPort());
        System.out.println("Press Ctrl + C to stop the server");
        System.out.println("(This works in command line but the Terminate button in Eclipse won't run the shutdown hook)");
        System.out.println("...");

        //attach shutdown hook
        Runtime.getRuntime().addShutdownHook(new ServerShutDownThread(this));

        running = true;

        //infinite loop accepting requests
        while (running)
        {
          //accepting the connection
          System.out.println("===========================");
          System.out.println("Listening to client request");
          Socket clientSocket = serverSocket.accept();

          processRequest(clientSocket);
        }
        
        if (!serverSocket.isClosed())
          serverSocket.close();
      } 
      catch (IOException e)
      {
        System.out.println("ERROR: Could not listen on port: " + config.getServerPort() + ". Or you shut down the server.");
        System.exit(-1);
      }
    }
    else //if the config file is not valid
    {
      System.out.println("ERROR: Cannot start server");
      System.out.println("ERROR: Config file is not valid...");

      System.out.println("ERROR DETAILS:\n");
      System.out.println(config.getErrorMessages());
    }
  }

  // this method handle each request from the client to the server
  public void processRequest(Socket cSocket)
  {
    try
    {

      //response to the client
      PrintWriter resp = new PrintWriter(cSocket.getOutputStream(), true);
      
      //request from the client
      BufferedReader req = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));

      //line in request
      String reqLine = req.readLine();
      String reqString = "";

// ====> after receiving a response, client will send null??????
      if (reqLine != null)
      {
        System.out.println("=============================================");
        System.out.println("Client request");
        System.out.println();

        do
        {
          //print request line to the console
          System.out.println(reqLine);
          reqString += reqLine + "\n";
        }
        while (!(reqLine = req.readLine()).equals(""));
        
        //process the request
        String[] lines = reqString.split("\n");
        //just care about the first line of the request
        String[] comps = lines[0].split(" ");

        //only process the GET request
        if (comps[0].equals("GET"))
        {
          System.out.println();
          System.out.println("Server response");
          System.out.println();

          //don't process the name/value pairs in the request
          comps[1] = comps[1].split("\\?")[0];

          //change / to index file
          if (comps[1].equals("/"))
          {
            File f = new File(config.convertToServerPath("\\index.htm"));

            if (f.exists())
              comps[1] = "/index.htm";
            else
              comps[1] = "/index.html";
          }

          File fServe = new File(config.convertToServerPath(comps[1]));
          
          String headers;
          //the full file name to output

          if (fServe.exists()) //file exist
          {
            //serve it to the client
            //headers
            headers = processHeader(200, comps[1]);
            filePath = fServe.getAbsolutePath();
          }
          else
          {
            //respond file not found to the client
            //headers
            headers = processHeader(404, "404.htm");

            fServe = new File("404.htm");
          }
          
          //write the headers to the client
          resp.print(headers);
          
          //print the headers to the server console
          System.out.print(headers);
          
          //flush the headers to the client
          resp.flush();

          //transfer the content to the client - Should transfer binary, not text -_-
          try
          {
            BufferedInputStream reader = new BufferedInputStream(new FileInputStream(fServe));
            BufferedOutputStream outContent = new BufferedOutputStream(cSocket.getOutputStream());
            byte[] buffer = new byte[1024];
            int read;
            
            while ((read = reader.read(buffer)) != -1)
            {
              outContent.write(buffer, 0, read);
              outContent.flush();
              
              //print to the server console only if not image file
              if (!ctype.contains("image"))
              {
                System.out.println(new String(buffer, 0, read));
              }
            }
            
            //if the output is image, print out the path
            if (ctype.contains("image"))
            {
              System.out.println(filePath);
            }
            
            reader.close();
            outContent.close();
          }
          catch (FileNotFoundException e1)
          {
            e1.printStackTrace();
          }
          catch (IOException e1)
          {
            e1.printStackTrace();
          }
        }
      }
      
      //close the client socket and input output streams
      resp.close();
      req.close();
      cSocket.close();
    }
    catch (IOException ex)
    {
      ex.printStackTrace();
    }
  }

  /*
   * Return correct header to send to the client
   */
  public String processHeader(int code, String fileName)
  {
    String result = "";

    if (code == 200)
    {
      result = "HTTP/1.1 200 OK\r\n";
    }
    else if (code == 404)
    {
      result = "HTTP/1.1 404 NOT FOUND\r\n";
    }

    DateFormat dateFormat = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss");
    Date date = new Date();
    result += "Date: " + dateFormat.format(date) + " GMT\r\n";
    result += "Server: Minh-Tuan Ta Simple Web Server\r\n";

    // set correct content type
    if (code == 200)
    {
      ctype = "text/html";
      String type = fileName.substring(fileName.indexOf('.') + 1);
      if (type.equals("css"))
      {
        ctype = "text/css";
      }
      else
      {
        for (int i = 0; i < config.getImageTypes().length; i++)
          if (type.equals(config.getImageTypes()[i]))
          {
            ctype = "image/" + config.getImageTypes()[i];
            break;
          }
      }

      result += "Content-Type: " + ctype + "\r\n";
      fileName = config.convertToServerPath(fileName);
    }
    else if (code == 404)
    {
      result += "Content-Type: text/html\r\n";
      fileName = "404.htm";
    }

    File f = new File(fileName);
    result += "Content-Length: " + f.length() + "\r\n";
    result += "Last-Modified: " + dateFormat.format(date) + " GMT\r\n";
    result += "Connection: close\r\n\r\n";

    return result;
  }
}
